/*******************************************************************************
 * Copyright ©  2011 Hulles Industries LLC <hulles.com>
 * All rights reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *     
 *******************************************************************************/

package com.hulles.sancho.engine;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import com.hulles.sancho.engine.DbManager.NoConnectionException;

/**
 * Various database-related utility functions.
 * @author mark
 */
final public class DbUtils {
    private static final String BKUPPREFIX = "sancho_";
    private static final String BKUPSUFFIX = ".sql";
    private DbManager dbMgr;
    private final SanchoApplication app;
    private final Preferences pref;
    private String backupCmd;
    private String backupOptions;
    private String toFolder;
    private String restoreCmd;
    private String restoreOptions;
    private File homeDirectory;

    /**
     * Create new instance of DbUtils
     */
    public DbUtils(SanchoApplication app) {
    	this.app = app;
        pref = Preferences.userNodeForPackage(this.getClass());
        dbMgr = DbManager.getInstance();
        homeDirectory = Utils.userHomeDirectory();
        this.backupCmd = pref.get("backupCommand", "mysqldump");
        this.backupOptions = pref.get("backupOptions", "");
        this.toFolder = pref.get("backupFolder", homeDirectory.getPath());
        this.restoreCmd = pref.get("restoreCommand", "mysql");
        this.restoreOptions = pref.get("restoreOptions", "");
        
        pref.addPreferenceChangeListener(new PreferenceChangeListener() {
            @Override
            public void preferenceChange(PreferenceChangeEvent evt) {
                if (evt.getKey().equals("backupCommand")) {
                    backupCmd = (evt.getNewValue());
                } else  if (evt.getKey().equals("backupOptions")) {
                    backupOptions = (evt.getNewValue());
                } else if (evt.getKey().equals("backupFolder")) {
                    toFolder = (evt.getNewValue());
                } else if (evt.getKey().equals("restpreCommand")) {
                    restoreCmd = (evt.getNewValue());
                } else if (evt.getKey().equals("restoreOptions")) {
                    restoreOptions = (evt.getNewValue());
                }
            }
        });
    }
    
    /**
     * Back up the MySQL database to (typically) a text file of MySQL DML and DDL
     * statements. The actual external program used to back up the database is user-defined;
     * the default program is <code>mysqldump</code>
     *
     * @return The filename of the backup or null if unable to create a backup file
     * @see restoreDatabase
     */
    public File backupDatabase() {
        Connection conn;
        ProcessBuilder procBuilder;
        Process process = null;
        String catalog = null;
        List<String> cmd;
        String err1;
        String err2;
        File backupFile = null;
        String line;
        File folder;
  
        if (backupCmd.length() == 0) {
            Utils.error("There is no database backup command defined.");
            return null;
        }
        folder = new File(toFolder);
        folder = Utils.getDirectory(folder);
        toFolder = folder.getPath();
        pref.put("backupFolder", toFolder);

        if (toFolder.length() == 0) {
            Utils.error("There is no database backup folder defined.");
            return null;
        }
        try {
            conn = dbMgr.getConnection();
        } catch (NoConnectionException ex) {
            Utils.error("No connection available");
            return null;
        }
        try {
            catalog = conn.getCatalog();
        } catch (SQLException ex) {
            Utils.sqlError(ex);
        }
        if (catalog == null) {
            return null;
        }
        dbMgr.closeConnection(conn);
        Utils.log(Level.FINE, "Starting backup");
        
        cmd = new ArrayList<String>();
        cmd.add(this.backupCmd);
        if (backupOptions.length() != 0) {
            cmd.add(backupOptions);
        }
        cmd.add("--user=" + app.getUserName());
        cmd.add("--password=" + app.getPassword());
        cmd.add(catalog);
        backupFile = new File(this.toFolder, makeFileName());
        cmd.add("--result-file=" + backupFile.getAbsolutePath());
        
        procBuilder = new ProcessBuilder(cmd);
        procBuilder.redirectErrorStream(true);
        try {
            process = procBuilder.start();
            InputStream is = process.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            Utils.log(Level.FINE, "Running" + commandString(cmd));
            while ((line = br.readLine()) != null) {
                Utils.log(Level.FINE, line);
            }
        } catch (IOException ex) {
            err1 = "Error occurred backing up the MySQL database. Check options.";
            err2 = String.format("Command string: ", commandString(cmd));
            Utils.error(err1, err2);
//            ex.printStackTrace();
        } finally {
            Utils.log(Level.FINE, "Backup completed");
        }
/*        
        if (process != null) {
            try {
                retVal = process.waitFor();
            } catch (InterruptedException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
*/
        return backupFile;
    }
    
    /**
     * Restore the database from (typically) a text file of MySQL DML and DDL statements such
     * as the one created by <code>backupDatabase</code>. The actual command used for the restore is
     * user-defined; <code>mysql</code> is the default command.
     * @param fromFile The file from which to restore the MySQL database
     * @return <code>true</code> if successful, <code>false</code> otherwise
     * @see backupDatabase
     */
    public boolean restoreDatabase(File fromFile) {
        Connection conn;
        ProcessBuilder procBuilder;
        Process process = null;
        List<String> cmd;
        String catalog = null;
        String err1;
        String err2;
        String line;

        if (fromFile == null) {
            Utils.error("System error: no file specified for restoreDatabase");
            return false;
        }
        if (restoreCmd.length() == 0) {
            Utils.error("There is no database restore command defined");
            return false;
        }
        
        try {
            conn = dbMgr.getConnection();
        } catch (NoConnectionException ex) {
            Utils.error("No connection available");
            return false;
        }
        try {
            catalog = conn.getCatalog();
        } catch (SQLException ex) {
            Utils.sqlError(ex);
        }
        if (catalog == null) {
            return false;
        }
        dbMgr.closeConnection(conn);
        Utils.log(Level.FINE, "Starting database restore");

        cmd = new ArrayList<String>();
        cmd.add(this.restoreCmd);
        if (restoreOptions.length() != 0) {
            cmd.add(restoreOptions);
        }
        cmd.add("--user=" + app.getUserName());
        cmd.add("--password=" + app.getPassword());
        cmd.add("--host=localhost");
        cmd.add(catalog);
        cmd.add("<" + fromFile.getAbsolutePath());
        procBuilder = new ProcessBuilder(cmd);
        procBuilder.redirectErrorStream(true);
        try {
            process = procBuilder.start();
            InputStream is = process.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            Utils.log(Level.FINE, "Running " + commandString(cmd));
            while ((line = br.readLine()) != null) {
                Utils.log(Level.FINE, line);
            }
        } catch (IOException ex) {
            err1 = "Error occurred restoring the MySQL database. Check options.";
            err2 = String.format("Command string: ", commandString(cmd));
            Utils.error(err1, err2);
//            ex.printStackTrace();
        } finally {
            Utils.log(Level.FINE, "Restore completed");
        }
/*        
        if (process != null) {
            try {
                retVal = process.waitFor();
            } catch (InterruptedException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
*/
        return true;
    }

    private String commandString(List<String> cmds) {
        StringBuffer sBuf;
        
        sBuf = new StringBuffer();
        for (String cmd : cmds) {
            sBuf.append(cmd);
            sBuf.append(' ');
        }
        // leave extra space on the end
        return sBuf.toString();
    }

    private String makeFileName() {
        StringBuilder fnmBuf;
        String dateStr;
        char ch;
        
        dateStr = dbMgr.getSQLDateStamp();
        if (dateStr == null) {
            Utils.error("Unable to get MySQL datestamp");
            return null;
        }
        fnmBuf = new StringBuilder(36);
        fnmBuf.append(BKUPPREFIX);
        
        fnmBuf.append(dateStr.replace(' ', '_'));
        for (int i=fnmBuf.length()-1; i>=0; i--) {
            ch = fnmBuf.charAt(i);
            switch (ch) {
                case ':':
                case '-':
                case '.':
                    fnmBuf.deleteCharAt(i);
                    break;
            }
        }
        fnmBuf.append(BKUPSUFFIX);
        return fnmBuf.toString();
    }
    
    /**
     * Get the path name of the current backup folder. This is stored in NbPreferences.
     * @return The path of the current backup folder.
     */
    public String getBackupFolderName() {
        return toFolder;
    }
}
